/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jbpm.pvm.internal.cal;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jbpm.api.JbpmException;

/**
 * represents a time duration.
 * 
 * <p>With the constructor {link {@link #Duration(String)} you can create a 
 * Duration from a text representation.  The syntax is as follows
 * </p>
 * 
 * <pre>
 * duration = part [',' part | 'and' part]*
 * part = number ['business'] unit
 * number = (0..9)+
 * unit = (y|year|years|month|months|w|week|weeks|d|day|days|h|hour|hours|min|minute|minutes|s|sec|second|seconds|milli|millis|millisecond|milliseconds)
 * </pre>
 * 
 * <p>Duration is immutable.
 * </p>
 */
public class Duration implements Serializable {

  private static final long serialVersionUID = 1L;

  boolean isBusinessTime;
  protected int millis;
  protected int seconds;
  protected int minutes;
  protected int hours;
  protected int days;
  protected int weeks;
  protected int months;
  protected int years;

  /** constructor for persistence.  note that this type is to be immutable. */
  protected Duration() {
  }

  /** parses the duration from a text 
   * 
   * duration = part [',' part | 'and' part]*
   * part = number ['business'] unit
   * number = (0..9)+
   * unit = (y|year|years|month|months|w|week|weeks|d|day|days|h|hour|hours|min|minute|minutes|s|sec|second|seconds|milli|millis|millisecond|milliseconds)
   * 
   * @throws JbpmException if the parsing is unsuccessful
   */
  public Duration(String text) {
    if (text==null) throw new JbpmException("text is null");

    for (String part: splitInParts(text)) {
      parsePart(part);
    }
    
    isBusinessTime = text.indexOf("business")!=-1;
  }

  public Duration(boolean isBusinessTime, int millis, int seconds, int minutes, int hours, int days, int weeks, int months, int years) {
    this.isBusinessTime = isBusinessTime;
    this.millis = millis;
    this.seconds = seconds;
    this.minutes = minutes;
    this.hours = hours;
    this.days = days;
    this.weeks = weeks;
    this.months = months;
    this.years = years;
  }
  
  private List<String> splitInParts(String text) {
    List<String> parts = new ArrayList<String>(2);
    while (text!=null) {
      int commaIndex = text.indexOf(',');
      int andIndex = text.indexOf(" and ");
      if ( ( (commaIndex==-1) 
             && (andIndex!=-1)
           ) 
           || 
           ( ( (commaIndex!=-1) 
               && (andIndex!=-1)
             ) 
             && (andIndex<commaIndex)
           )
         ) {
        String part = text.substring(0, andIndex).trim();
        parts.add(part);
        text = text.substring(andIndex+5);
        
      } else if ( ( (commaIndex!=-1) 
                    && (andIndex==-1)
                  ) 
                  || 
                  ( ( (commaIndex!=-1) 
                      && (andIndex!=-1)
                    ) 
                    && (andIndex>commaIndex)
                  )
                ) {
        String part = text.substring(0, commaIndex).trim();
        parts.add(part);
        text = text.substring(commaIndex+1);
        
      } else {
        parts.add(text.trim());
        text = null;
      }
    }

    return parts;
  }

  private void parsePart(String part) {
    int spaceIndex = part.indexOf(' ');
    if (spaceIndex==-1) {
      throw new JbpmException("couldn't parse duration part "+part);
    }
    String quantityText = part.substring(0, spaceIndex).trim();
    spaceIndex = part.lastIndexOf(' ');
    String unitText = part.substring(spaceIndex+1).trim().toLowerCase();
    
    int quantity;
    try {
      quantity = Integer.parseInt(quantityText);
    } catch (NumberFormatException e) {
      throw new JbpmException("couldn't parse quantity "+quantityText+" in duration text", e);
    }
    FieldSetter fieldSetter = fieldSetters.get(unitText);
    if (fieldSetter==null) {
      throw new JbpmException("couldn't parse quantity "+quantityText);
    }
    fieldSetter.set(this, quantity);
  }
  
  private interface FieldSetter {
    void set(Duration duration, int quantity);
  }
  private static class MillisSetter implements FieldSetter {
    public void set(Duration duration, int quantity) {
      duration.millis = quantity;
    }
  }
  private static class SecondSetter implements FieldSetter {
    public void set(Duration duration, int quantity) {
      duration.seconds = quantity;
    }
  }
  private static class MinuteSetter implements FieldSetter {
    public void set(Duration duration, int quantity) {
      duration.minutes = quantity;
    }
  }
  private static class HourSetter implements FieldSetter {
    public void set(Duration duration, int quantity) {
      duration.hours = quantity;
    }
  }
  private static class DaySetter implements FieldSetter {
    public void set(Duration duration, int quantity) {
      duration.days = quantity;
    }
  }
  private static class WeekSetter implements FieldSetter {
    public void set(Duration duration, int quantity) {
      duration.weeks = quantity;
    }
  }
  private static class MonthSetter implements FieldSetter {
    public void set(Duration duration, int quantity) {
      duration.months = quantity;
    }
  }
  private static class YearSetter implements FieldSetter {
    public void set(Duration duration, int quantity) {
      duration.years = quantity;
    }
  }

  private static final Map<String, FieldSetter> fieldSetters = new HashMap<String, FieldSetter>();
  static {
    FieldSetter fieldSetter = new MillisSetter();
    fieldSetters.put("milli", fieldSetter);
    fieldSetters.put("millis", fieldSetter);
    fieldSetters.put("millisecond", fieldSetter);
    fieldSetters.put("milliseconds", fieldSetter);

    fieldSetter = new SecondSetter();
    fieldSetters.put("s", fieldSetter);
    fieldSetters.put("sec", fieldSetter);
    fieldSetters.put("second", fieldSetter);
    fieldSetters.put("seconds", fieldSetter);

    fieldSetter = new MinuteSetter();
    fieldSetters.put("min", fieldSetter);
    fieldSetters.put("minute", fieldSetter);
    fieldSetters.put("minutes", fieldSetter);

    fieldSetter = new HourSetter();
    fieldSetters.put("h", fieldSetter);
    fieldSetters.put("hour", fieldSetter);
    fieldSetters.put("hours", fieldSetter);

    fieldSetter = new DaySetter();
    fieldSetters.put("d", fieldSetter);
    fieldSetters.put("day", fieldSetter);
    fieldSetters.put("days", fieldSetter);

    fieldSetter = new WeekSetter();
    fieldSetters.put("w", fieldSetter);
    fieldSetters.put("week", fieldSetter);
    fieldSetters.put("weeks", fieldSetter);

    fieldSetter = new MonthSetter();
    fieldSetters.put("month", fieldSetter);
    fieldSetters.put("months", fieldSetter);

    fieldSetter = new YearSetter();
    fieldSetters.put("y", fieldSetter);
    fieldSetters.put("year", fieldSetter);
    fieldSetters.put("years", fieldSetter);
  }
  
  public int getDays() {
    return days;
  }

  
  public int getHours() {
    return hours;
  }

  
  public boolean isBusinessTime() {
    return isBusinessTime;
  }

  
  public int getMillis() {
    return millis;
  }

  
  public int getMinutes() {
    return minutes;
  }

  
  public int getMonths() {
    return months;
  }

  
  public int getSeconds() {
    return seconds;
  }

  
  public int getWeeks() {
    return weeks;
  }

  
  public int getYears() {
    return years;
  }
}
